/*-
 * $Id: TerminalMain.java 4 2010-11-04 07:39:56Z andrewbass $
 */

package com.google.code.getwrong.terminalui;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.List;

/**
 * @author Andrew ``Bass'' Shcheglov (andrewbass@gmail.com)
 * @author $Author: andrewbass $
 * @version $Revision: 4 $, $Date:: 2010-11-04 07:39:56 +0000 #$
 * @todo learn about ^[11m hack
 */
abstract class TerminalMain {
	private static final int DEFAULT_NUMBER_OF_COLORS = 2;

	
	/**
	 * ^[
	 */
	static final char ESC = '\u001B';


	private static final int A_OFF = 0;

	private static final int A_BOLD = 1;

	private static final int A_UNDERSCORE = 4;

	private static final int A_BLINK = 5;

	private static final int A_REVERSE = 7;

	private static final int A_CONCEALED = 8;


	private static final char C_LIGHT_SHADE = '\u2591';

	private static final char C_MEDIUM_SHADE = '\u2592';

	private static final char C_DARK_SHADE = '\u2593';

	private static final char C_FULL_BLOCK = '\u2588';


	private TerminalMain() {
		assert false;
	}

	/**
	 * Requires tput from ncurses to be present in the PATH.
	 *
	 * On Solaris, /usr/bin/tput is a part of SUNWcsu.
	 *
	 * @param arg
	 * @throws IOException
	 * @throws InterruptedException
	 */
	private static List<String> invokeTput(final String ... arg)
	throws IOException, InterruptedException {
		final String cmdarray[] = new String[arg.length + 1];
		cmdarray[0] = "tput";
		System.arraycopy(arg, 0, cmdarray, 1, arg.length);
		final Process process = Runtime.getRuntime().exec(cmdarray);

		final List<String> errLines = new LinkedList<String>();
		final BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()));
		for (String s = err.readLine(); s != null; s = err.readLine()) {
			errLines.add(s);
		}
		err.close();

		final List<String> outLines = new LinkedList<String>();
		final BufferedReader out = new BufferedReader(new InputStreamReader(process.getInputStream()));
		for (String s = out.readLine(); s != null; s = out.readLine()) {
			outLines.add(s);
		}
		out.close();

		final int exitCode = process.waitFor();

		if (exitCode != 0 || !errLines.isEmpty()) {
			for (final String s : errLines) {
				System.err.println(s);
			}

			if (exitCode != 0) {
				System.out.println("tput exited with code " + exitCode);
			}

			throw new IOException();
		}

		return outLines;
	}

	private static int getNumberOfColors() {
		try {
			final List<String> outLines = invokeTput("colors");

			if (outLines.size() != 1) {
				for (final String s : outLines) {
					System.out.println(s);
				}

				return DEFAULT_NUMBER_OF_COLORS;
			}

			try {
				return Integer.parseInt(outLines.iterator().next());
			} catch (final NumberFormatException nfe) {
				return DEFAULT_NUMBER_OF_COLORS;
			}
		} catch (final IOException ioe) {
			return DEFAULT_NUMBER_OF_COLORS;
		} catch (final InterruptedException ie) {
			return DEFAULT_NUMBER_OF_COLORS;
		}
	}

	private static String getTermName() {
		return System.getenv("TERM");
	}

	/**
	 * Doesn't work as tput is invoked in the background.
	 * Should rely on SIGWINCH instead.
	 *
	 * @todo ensure that tput doesn't fit on Solaris, either
	 * @todo implement via stty -a (coreutils/SUNWcsu/SUNWxcu4), if possible. 
	 */
	private static int getScreenWidth() {
		try {
			final List<String> outLines = invokeTput("cols");

			if (outLines.size() != 1) {
				for (final String s : outLines) {
					System.out.println(s);
				}

				return -1;
			}

			try {
				return Integer.parseInt(outLines.iterator().next());
			} catch (final NumberFormatException nfe) {
				return -1;
			}
		} catch (final IOException ioe) {
			return -1;
		} catch (final InterruptedException ie) {
			return -1;
		}
	}

	/**
	 * Only works when COLUMNS and LINES get exported.
	 * Should rely on SIGWINCH instead.
	 */
	private static int getScreenWidth0() {
		final String columns = System.getenv("COLUMNS");
		if (columns == null || columns.length() == 0) {
			return -1;
		}
		try {
			return Integer.parseInt(columns);
		} catch (final NumberFormatException nfe) {
			return -1;
		}
	}

	/**
	 * Doesn't work as tput is invoked in the background.
	 * Should rely on SIGWINCH instead.
	 *
	 * @todo ensure that tput doesn't fit on Solaris, either
	 * @todo implement via stty -a (coreutils/SUNWcsu/SUNWxcu4), if possible. 
	 */
	private static int getScreenHeight() {
		try {
			final List<String> outLines = invokeTput("lines");

			if (outLines.size() != 1) {
				for (final String s : outLines) {
					System.out.println(s);
				}

				return -1;
			}

			try {
				return Integer.parseInt(outLines.iterator().next());
			} catch (final NumberFormatException nfe) {
				return -1;
			}
		} catch (final IOException ioe) {
			return -1;
		} catch (final InterruptedException ie) {
			return -1;
		}
	}

	/**
	 * Only works when COLUMNS and LINES get exported.
	 * Should rely on SIGWINCH instead.
	 */
	private static int getScreenHeight0() {
		final String lines = System.getenv("LINES");
		if (lines == null || lines.length() == 0) {
			return -1;
		}
		try {
			return Integer.parseInt(lines);
		} catch (final NumberFormatException nfe) {
			return -1;
		}
	}

	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(final String args[]) throws IOException {
		final String termName = getTermName();
		if (termName == null || termName.length() == 0) {
			System.err.println("TERM variable is not defined.");
			return;
		}

		final PrintWriter out = new PrintWriter(System.out);

		final Terminal terminal = new Terminal(out, false);
		final Terminal terminalExt = new Terminal(out, true);

		final int numberOfColors = getNumberOfColors();
		final int screenWidth = getScreenWidth();
		final int screenHeight = getScreenHeight();

		terminal.println(termName);
		terminal.println("Colors supported: " + numberOfColors);
		terminal.println("Pseudo-graphics supported: " + terminal.supportsPseudoGraphics());
		if (screenWidth == -1 || screenHeight == -1) {
			terminal.println("Screen dimensions: unknown.");
		} else {
			terminal.println("Screen dimensions: " + screenWidth + 'x' + screenHeight + ".");
		}

		if (numberOfColors < 8) {
			return;
		}

		terminal.setForeground(Color.RED);
		terminal.setGraphicsMode(A_BOLD);
		terminal.println("Hello, World!");


		terminal.setGraphicsMode(A_OFF);
		terminal.setForeground(Color.WHITE);
		terminal.setBackground(Color.BLACK);
		terminal.print(' ');
		terminal.print(C_LIGHT_SHADE);
		terminal.print(C_MEDIUM_SHADE);
		terminal.print(C_DARK_SHADE);
		terminal.print(C_FULL_BLOCK);
		terminal.println();
		terminal.setGraphicsMode(A_OFF);
		terminal.setForeground(Color.BLACK);
		terminal.setBackground(Color.WHITE);
		terminal.print(C_FULL_BLOCK);
		terminal.print(C_DARK_SHADE);
		terminal.print(C_MEDIUM_SHADE);
		terminal.print(C_LIGHT_SHADE);
		terminal.print(' ');
		terminal.println();
		
		terminal.setGraphicsMode(A_OFF);
		terminal.println("Press Enter to proceed to the next screen.");
		terminal.flush();
		System.in.read();
		terminal.clear();		

		int xOffset = 10;
		int yOffset = 8;
		terminal.setGraphicsMode(A_BOLD);
		terminal.setForeground(Color.BLACK);
		terminal.setBackground(Color.GREEN);
		for (int x = xOffset; x < screenWidth - xOffset; x++) {
			for (int y = yOffset; y < screenHeight - yOffset; y++) {
				terminal.moveTo(x, y);
				out.append(' ');
			}
		}

		xOffset++;
		yOffset++;
		terminal.setGraphicsMode(A_BOLD);
		terminal.setForeground(Color.RED);
		terminal.setBackground(Color.BLUE);
		for (int x = xOffset; x < screenWidth - xOffset; x++) {
			for (int y = yOffset; y < screenHeight - yOffset; y++) {
				terminal.moveTo(x, y);
				out.append(C_DARK_SHADE);
			}
		}

		xOffset++;
		yOffset++;
		terminal.setGraphicsMode(A_BOLD);
		terminal.setForeground(Color.RED);
		terminal.setBackground(Color.BLUE);
		for (int x = xOffset; x < screenWidth - xOffset; x++) {
			for (int y = yOffset; y < screenHeight - yOffset; y++) {
				terminal.moveTo(x, y);
				out.append(C_MEDIUM_SHADE);
			}
		}

		xOffset++;
		yOffset++;
		terminal.setGraphicsMode(A_BOLD);
		terminal.setForeground(Color.RED);
		terminal.setBackground(Color.BLUE);
		for (int x = xOffset; x < screenWidth - xOffset; x++) {
			for (int y = yOffset; y < screenHeight - yOffset; y++) {
				terminal.moveTo(x, y);
				out.append(C_LIGHT_SHADE);
			}
		}

		terminal.setGraphicsMode(A_OFF);
		terminal.moveTo(0, 0);
		terminal.println("Press Enter to proceed to the next screen.");
		terminal.flush();
		System.in.read();
		terminal.clear();

		terminal.moveTo(0, 1);

		terminal.setGraphicsMode(A_OFF);

		terminal.setForeground(Color.RED);
		terminal.println("Red text (16-color term)");

		terminalExt.setForeground(Color.RED);
		terminalExt.println("Red text (256-color term)");

		terminalExt.setForeground(Color.RED_BRIGHT);
		terminalExt.println("Bright red text (256-color term)");

		terminal.setGraphicsMode(A_OFF);
		terminal.moveTo(0, 0);
		terminal.println("Press Enter to proceed to the next screen.");
		terminal.flush();
		System.in.read();
		terminal.clear();

		final Component border0 = new Border(terminal,
				new Rectangle(new Point(0, 1), new Dimension(20, 10)),
				new BorderStyle(BorderStyleAtom.SINGLE, BorderStyleAtom.SINGLE),
				Color.MAGENTA,
				Color.BLUE);
		border0.paint();

		final Component border1 = new Border(terminal,
				new Rectangle(new Point(20, 5), new Dimension(20, 10)),
				new BorderStyle(BorderStyleAtom.DOUBLE, BorderStyleAtom.DOUBLE),
				Color.GREEN,
				Color.BLACK);
		border1.paint();

		final Component border2 = new Border(terminal,
				new Rectangle(new Point(40, 9), new Dimension(20, 10)),
				new BorderStyle(BorderStyleAtom.DOUBLE, BorderStyleAtom.SINGLE),
				Color.RED,
				Color.BLACK);
		border2.paint();

		final Component border3 = new Border(terminal,
				new Rectangle(new Point(60, 13), new Dimension(20, 10)),
				new BorderStyle(BorderStyleAtom.SINGLE, BorderStyleAtom.DOUBLE),
				Color.BLACK,
				Color.WHITE);
		border3.paint();

		terminal.setGraphicsMode(A_OFF);
		terminal.moveTo(0, 0);
		terminal.println("Press Enter to terminate.");
		terminal.flush();
		System.in.read();
		terminal.clear();
	}
}
